package com.mzkj.wanjulaile.mobile.api.modules.weChat.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.mzkj.common.utils.*;
import com.mzkj.wanjulaile.mobile.api.common.config.Global;
import com.mzkj.wanjulaile.mobile.api.common.utils.RedisUtil;
import com.mzkj.wanjulaile.mobile.api.modules.weChat.entity.*;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import com.thoughtworks.xstream.io.xml.XppDriver;
import org.activiti.engine.impl.util.json.JSONObject;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * Copyright © 2016 上海美挚网络. All rights reserved.
 *
 * @Projec: meilein
 * @Package:in.meile.print.modules.mobile.util
 * @Description:微信工具类
 * @author:bing
 * @date:16/5/3 下午3:07
 * @version:V1.0
 */
public class WechatUtils {

	public static final String GET_AUTHTOKEN_URL = " https://api.weixin.qq.com/sns/oauth2/access_token?";
//    public static final String USER_INFO_URL = "https://api.weixin.qq.com/cgi-bin/user/info";
	public static final String USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo?";

    protected static Logger logger = LoggerFactory.getLogger(WechatUtils.class);

    private static final String TICKET_URL_PREFIX = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=";
    private static final String TICKET_URL_SUFFIX = "&type=jsapi";

	//获取二维码ticket的url
	private static final String QRCODE_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=";
	/**
	 * 验证消息是否由微信服务器发送
	 * @param signature
	 * @param timestamp
	 * @param nonce
	 * @return
	 */
	public static boolean checkSignature(String signature,String timestamp,String nonce){
		String[] array = new String[]{WxConstant.TOKEN,timestamp,nonce};
		//将三个参数进行字典排序
		Arrays.sort(array);
		StringBuilder content = new StringBuilder();
		for (int i=0; i<array.length; i++){
			content.append(array[i]);
		}
		MessageDigest messageDigest;
		String tempStr = null;
		try {
			messageDigest = MessageDigest.getInstance("SHA-1");
			// 将三个参数字符串拼接成一个字符串进行sha1加密
			byte[] digest = messageDigest.digest(content.toString().getBytes());
			tempStr = byteToStr(digest);
		} catch (Exception e) {
			logger.error("验证微信服务器url异常",e);
			throw new RuntimeException("验证微信服务器url异常",e);
		}
		return tempStr != null ? tempStr.equalsIgnoreCase(signature) : false;
	}

	/**
	 * 检验数据是否是由微信服务器发出
	 * @return
	 */
	public static boolean checkResponseSign(SortedMap<String,Object> map){
		String resSign = (String) map.get("sign");
		String checkSign = getSign(map);
		return checkSign.equals(resSign);
	}

	public static AuthToken getTokenByAuthCode(HttpServletRequest request){
        String code = request.getParameter("code");
        HttpSession session = request.getSession();
		AuthToken authToken;
		StringBuilder json = new StringBuilder();
		try {
			URL url = new URL(GET_AUTHTOKEN_URL+"appid="+ WxConstant.APP_ID+"&secret="+ WxConstant.APP_SECRET+"&code="+code+"&grant_type=authorization_code");
			URLConnection uc = url.openConnection();
			BufferedReader in = new BufferedReader(new InputStreamReader(uc.getInputStream()));
			String inputLine ;
			while((inputLine=in.readLine())!=null){
				json.append(inputLine);
			}
			in.close();
			authToken = parseJsonToObject(json.toString());
			if (null != authToken && StringUtils.isEmpty(authToken.getErrcode())){
				session.setAttribute("authToken", authToken);
			}
		} catch (Exception e) {
			logger.error("微信工具类:根据授权code获取access_token异常",e);
			throw new RuntimeException("微信工具类:根据授权code获取access_token异常",e);
		}
		return authToken;
	}

	public static AuthToken getTokenByAuthCode(String code){
		AuthToken authToken;
		StringBuilder json = new StringBuilder();
		try {
			URL url = new URL(GET_AUTHTOKEN_URL+"appid="+ WxConstant.APP_ID+"&secret="+ WxConstant.APP_SECRET+"&code="+code+"&grant_type=authorization_code");
			URLConnection uc = url.openConnection();
			BufferedReader in = new BufferedReader(new InputStreamReader(uc.getInputStream()));
			String inputLine ;
			while((inputLine=in.readLine())!=null){
				json.append(inputLine);
			}
			in.close();
			authToken = parseJsonToObject(json.toString());
//			if (null != authToken && StringUtils.isEmpty(authToken.getErrcode())){
////				session.setAttribute("authToken", authToken);
//			}
		} catch (Exception e) {
			logger.error("微信工具类:根据授权code获取access_token异常",e);
			throw new RuntimeException("微信工具类:根据授权code获取access_token异常",e);
		}
		return authToken;
	}

	public static AuthToken parseJsonToObject(String jsonStr) {
		ObjectMapper om = new ObjectMapper();
		AuthToken authToken;
		try {
			authToken= om.readValue(jsonStr,AuthToken.class);
		} catch (Exception e) {
			logger.error("解析token返回结果异常",e);
			throw new RuntimeException("解析token返回结果异常",e);
		}
		return authToken;
	}

	/**
	 * 解析微信服务器发来的请求[转发用户消息]
	 * @param inputStream
	 * @return
	 * @throws Exception
	 */
	public static SortedMap<String,Object> parseXml(InputStream inputStream) {
		SortedMap<String,Object> map = Maps.newTreeMap();
		try {
			//获取request输入流
			SAXReader reader = new SAXReader();
			Document document = reader.read(inputStream);
			//得到xml根元素
			Element root = document.getRootElement();
			//得到根元素所有节点
			List<Element> elementList = root.elements();
			//遍历所有子节点
			for (Element element:elementList){
				map.put(element.getName(),element.getText());
			}
			//释放资源
			inputStream.close();
		} catch (Exception e) {
			logger.error("微信工具类:解析xml异常",e);
			throw new RuntimeException("微信工具类:解析xml异常",e);
		}
		return map;
	}

    public static SortedMap<String,Object> parseXml(String string) {
        SortedMap<String,Object> map = Maps.newTreeMap();
        try {
            InputStream inputStream = new ByteArrayInputStream(string.getBytes("utf8"));
            //获取request输入流
            SAXReader reader = new SAXReader();
            Document document = reader.read(inputStream);
            //得到xml根元素
            Element root = document.getRootElement();
            //得到根元素所有节点
            List<Element> elementList = root.elements();
            //遍历所有子节点
            for (Element element:elementList){
                map.put(element.getName(),element.getText());
            }
            //释放资源
            inputStream.close();
        } catch (Exception e) {
            throw new RuntimeException("微信工具类:解析xml异常",e);
        }
        return map;
    }

	/**
	 * 文本消息转换成xml
	 * @param textMessage
	 * @return
	 */
	public static String textMessageToXml(ResTextMessage textMessage){
		xstream.autodetectAnnotations(true);
		xstream.alias("xml",textMessage.getClass());
		return xstream.toXML(textMessage);
	}

	/**
	 * 统一下单请求转换成xml
	 * @param data
	 * @return
	 */
	public static String wxSendDataToXml(WxPaySendData data){
		xStream2.autodetectAnnotations(true);
		xStream2.alias("xml", WxPaySendData.class);
		return xStream2.toXML(data);
	}

	public static String wxSendDataToXml(WxRefundData data){
		xStream2.autodetectAnnotations(true);
		xStream2.alias("xml", WxRefundData.class);
		return xStream2.toXML(data);
	}

	/**
	 * 扩展xstream,使其支持name带有"_"的节点
	 */
	public static XStream xStream2 = new XStream(new DomDriver("UTF-8",new XmlFriendlyNameCoder("-_","_")));

	/**
	 * 扩展xstream，使其支持CDATA块
	 */
	private static XStream xstream = new XStream(new XppDriver() {
		@Override
		public HierarchicalStreamWriter createWriter(Writer out) {
			return new PrettyPrintWriter(out) {
				// 对所有xml节点的转换都增加CDATA标记
				boolean cdata = true;
				@SuppressWarnings("unchecked")
				@Override
				public void startNode(String name, Class clazz) {
					super.startNode(name, clazz);
				}
				@Override
				protected void writeText(QuickWriter writer, String text) {
					if (cdata) {
						writer.write("<![CDATA[");
						writer.write(text);
						writer.write("]]>");
					} else {
						writer.write(text);
					}
				}
			};
		}
	});

	/**
	 * 获取微信支付签名
	 * @param map
	 * @return
	 */
	public static String getSign(Map<String,Object> map){
		StringBuffer sb = new StringBuffer();
		Set set = map.entrySet();
		Iterator iterator = set.iterator();
		while (iterator.hasNext()){
			Map.Entry entry = (Map.Entry) iterator.next();
			String k = (String) entry.getKey();
			Object v = entry.getValue();
			if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){
				sb.append(k + "=" + v + "&");
			}
		}
		sb.append("key=" + WxPayConfig.KEY);
		String sign = MD5.MD5Encode(sb.toString()).toUpperCase();
		return sign;
	}

	/**
	 * 根据支付来源获取微信支付签名
	 * @param map
	 * @return
	 */
	public static String getSignByResource(Map<String,Object> map,String resource){
		StringBuffer sb = new StringBuffer();
		Set set = map.entrySet();
		Iterator iterator = set.iterator();
		while (iterator.hasNext()){
			Map.Entry entry = (Map.Entry) iterator.next();
			String k = (String) entry.getKey();
			Object v = entry.getValue();
			if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){
				sb.append(k + "=" + v + "&");
			}
		}
		String key = WxPayConfig.KEY;
		if(WxPayConfig.RESOURCE_PAY_WEB.equals(resource)){
			key = WxPayConfig.WEB_KEY;
		}
		sb.append("key=" + key);
		String sign = MD5.MD5Encode(sb.toString()).toUpperCase();
		return sign;
	}

	/**
	 * 获取指定长度的随机字符串
	 * @param length
	 * @return
	 */
	public static String getRandomStr(int length){
		String base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
		Random random = new Random();
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < length; i++) {
			int number = random.nextInt(base.length());
			sb.append(base.charAt(number));
		}
		return sb.toString();
	}

	/**
	 *  获取当前时间戳
	 * @return 字符串
	 */
	public static String getTimeStamp(){
		return String.valueOf(System.currentTimeMillis()/1000);
	}

	/**
	 * 将字节数组转换成字符串
	 * @param byteArray
	 * @return
	 */
	private static String byteToStr(byte[] byteArray) {
		String strDigest = "";
		for (int i = 0; i < byteArray.length; i++) {
			strDigest += byteToHexStr(byteArray[i]);
		}
		return strDigest;
	}

	/**
	 * 将自字节转换成16进制
	 * @param mByte
	 * @return
	 */
	private static String byteToHexStr(byte mByte) {
		char[] digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
		char[] tempArr = new char[2];
		tempArr[0] = digit[(mByte >>> 4) & 0X0F];
		tempArr[1] = digit[mByte & 0X0F];
		return new String(tempArr);
	}

    public static void getTicket() {
//        String accessToken = JedisUtils.get(WxConstant.ACCESS_TOKEN);
		String accessToken = (String)RedisUtil.get(WxConstant.ACCESS_TOKEN);
        getTicket(accessToken);
    }

    public static void getTicket(String accessToken) {
        StringBuilder json = new StringBuilder();
        try {
            URL urlObject = new URL(TICKET_URL_PREFIX + accessToken + TICKET_URL_SUFFIX);
            URLConnection uc = urlObject.openConnection();
            BufferedReader in = new BufferedReader(new InputStreamReader(uc.getInputStream()));
            String inputLine ;
            while((inputLine=in.readLine())!=null){
                json.append(inputLine);
            }
            in.close();
            JSONObject jsonObject = new JSONObject(json.toString());
            if((Integer)jsonObject.get("errcode") == 0) {
                String ticket = jsonObject.getString("ticket");
                CacheUtils.put(WxConstant.TICKET, ticket);
                logger.info("刷新ticket====》" + ticket);
            }else {
                logger.error(jsonObject.get("errcode").toString());
                logger.error(jsonObject.get("errmsg").toString());
            }
        } catch (Exception e) {
            logger.error("获取jsapi_ticket失败",e);
        }
    }

    public static Map<String, String> sign(String jsapiTicket, String url) {
        Map<String, String> ret = new HashMap<String, String>();
        String nonceStr = createNonceStr();
        String timestamp = createTimestamp();
        String string1;
        String signature = "";

        //注意这里参数名必须全部小写，且必须有序
        string1 = "jsapi_ticket=" + jsapiTicket +
                "&noncestr=" + nonceStr +
                "&timestamp=" + timestamp +
                "&url=" + url;
//        System.out.println(string1);

        try
        {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        }catch (NoSuchAlgorithmException e){
            logger.error("加密出错",e);
        }catch (UnsupportedEncodingException e){
            logger.error("加密出错",e);
        }

        ret.put("url", url);
        ret.put("jsapi_ticket", jsapiTicket);
        ret.put("nonceStr", nonceStr);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);

        return ret;
    }

    public static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash)
        {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    public static String createNonceStr() {
        return UUID.randomUUID().toString();
    }

    private static String createTimestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }

	/**
	 * 获取js-sdk签名信息
	 * @param jsapiTicket
	 * @return
     */
    public static WxJsParam signature(String jsapiTicket) {
        WxJsParam wxJsParam = new WxJsParam();
        String nonceStr = createNonceStr();
        String timestamp = createTimestamp();
        String string1;
        String signature = "";
		String url = "http://" + Global.getConfig("wx.web.domain")+"/";
        string1 = "jsapi_ticket=" + jsapiTicket +
                "&noncestr=" + nonceStr +
                "&timestamp=" + timestamp +
                "&url=" + url;
        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        } catch (Exception e) {
            logger.error("加密出错", e);
        }
        wxJsParam.setNonceStr(nonceStr);
        wxJsParam.setTimestamp(timestamp);
        wxJsParam.setSignature(signature);
        wxJsParam.setJsapiTicket(jsapiTicket);
		wxJsParam.setAppId(WxConstant.APP_ID);
		return wxJsParam;
    }

	public static String getQrcodeTicket(Integer scene_id,String accessToken){
		String url = QRCODE_TICKET_URL + accessToken;
		String params = "{\"action_name\": \"QR_LIMIT_SCENE\", \"action_info\": {\"scene\": {\"scene_id\": "+scene_id+"}}}";
		String qrcodeJson = HttpUtils.doPost(url,params);
		JSONObject jsonObject = new JSONObject(qrcodeJson);
		return jsonObject.getString("ticket");
	}

}
